Die Boost C++ Bibliotheken


Kapitel 15: Fehlerbehandlung


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


15.1 Allgemeines

Funktionen, die fehlschlagen können, müssen in irgendeiner Weise den Aufrufer informieren, wenn sie fehlschlagen. In C++ geschieht dies entweder über den Rückgabewert von Funktionen oder über Ausnahmen, die von Funktionen geworfen werden können. Der Rückgabewert wird dabei üblicherweise dann verwendet, wenn davon ausgegangen werden kann, dass ein Fehler nicht allzu außergewöhnlich ist. Es kann also erwartet werden, dass der Aufrufer den Rückgabewert überprüft, um gegebenenfalls auf einen Fehler zu reagieren.

Ausnahmen hingegen werden dann geworfen, wenn etwas außergewöhnliches passiert ist, von dem man normalerweise erwartet, dass es nicht passiert. Ein gutes Beispiel ist die Ausnahme vom Typ std::bad_alloc, die dann geworfen wird, wenn eine dynamische Speicherreservierung mit new fehlschlägt. Da in den allermeisten Fällen mit new Speicher ohne Probleme reserviert werden kann, wäre es lästig, andauernd einen Rückgabewert überprüfen zu müssen.

In diesem Kapitel lernen Sie zwei Boost C++ Bibliotheken kennen, die Sie bei der Fehlerbehandlung unterstützen: Boost.System übersetzt betriebssystemspezifische in plattformunabhängige Fehlerarten. So können zum Beispiel Funktionen mit einem Rückgabewert, der auf einem betriebssystemspezifischen Typen basiert, dank Boost.System plattformunabhängig gestaltet werden. Boost.Exception wiederum gestattet es, zusätzliche Informationen in eine Ausnahme hineinzupacken, so dass in einem catch-Block mehr Informationen über eine Ausnahme zur Verfügung stehen, womit wohlmöglich besser auf die Ausnahme reagiert werden kann.


15.2 Boost.System

Boost.System ist eine kleine Bibliothek, die vier Klassen zur Identifikation von Fehlern definiert. Die einfachste Klasse ist boost::system::error_code, die betriebssystemspezifische Fehler repräsentiert. Da Betriebssysteme typischerweise Fehler einfach durchnummerieren, speichert boost::system::error_code in einer int-Variablen einen Fehlercode. Im folgenden Beispiel, in dem auf die Bibliothek Boost.Asio zugegriffen wird, sehen Sie, wie diese Klasse verwendet wird.

#include <boost/system/error_code.hpp> 
#include <boost/asio.hpp> 
#include <iostream> 
#include <string> 

int main() 
{ 
  boost::system::error_code ec; 
  std::string hostname = boost::asio::ip::host_name(ec); 
  std::cout << ec.value() << std::endl; 
} 

Boost.Asio bietet eine freistehende Funktion boost::asio::ip::host_name() an. Diese Funktion gibt den Namen des Computers zurück, auf dem das Programm ausgeführt wird.

Der Funktion boost::asio::ip::host_name() kann als einziger Parameter ein Objekt vom Typ boost::system::error_code übergeben werden. Wenn die Betriebssystemfunktion, auf der boost::asio::ip::host_name() basiert, fehlschlägt, kann über diesen Parameter der entsprechende Fehlercode erhalten werden. Es ist auch möglich, boost::asio::ip::host_name() ohne Parameter aufzurufen, falls Sie nicht an einem eventuellen Fehlercode interessiert sind.

Die Funktion boost::asio::ip::host_name() bietet sich deswegen als Beispiel an, weil ihre Implementation in Boost 1.36.0 fehlerhaft war. So gab die Funktion unter Umständen selbst dann einen Fehlercode zurück, wenn die zugrundeliegende Betriebssystemfunktion fehlerfrei den Namen des Computers ermittelt hatte. In der Version 1.37.0 wurde der Fehler behoben, so dass Sie, wenn Sie mit einer aktuellen Boost-Version arbeiten, boost::asio::ip::host_name() ohne Bedenken verwenden können.

Da ein Fehlercode lediglich eine Zahl ist, können Sie mit Hilfe der Methode value() auf die Variable ec zugreifen und den Fehlercode ausgeben. Während ein Fehlercode 0 bedeutet, dass kein Fehler vorliegt, hängt die Bedeutung jeder anderen Zahl vom Betriebssystem ab. Sie müssen entsprechend in der Dokumentation Ihres Betriebssystems nachschlagen.

Für obiges Programm, kompiliert mit Visual Studio 2008 unter Windows XP, habe ich mit Boost 1.36.0 wiederholt den Fehlercode 14 erhalten. Dieser Fehlercode bedeutet unter Windows, dass nicht genügend Speicherplatz vorhanden ist, um eine Operation auszuführen. Obwohl boost::asio::ip::host_name() erfolgreich den Namen des Computers ermittelt hat, wurde im Parameter der Fehlercode 14 gemeldet. Hierbei handelte es sich wie bereits erwähnt um einen Fehler in der Implementation von boost::asio::ip::host_name().

Neben value() bietet die Klasse boost::system::error_code eine Methode category() an. Diese führt uns zur zweiten Klasse, die Boost.System definiert: category() gibt ein Objekt vom Typ boost::system::category zurück.

Fehlercodes sind wie bereits erfahren einfach nur Zahlen. Während ein Betriebssystemhersteller wie Microsoft darauf achten kann, dass Zahlen jeweils eindeutig einen Betriebssystemfehler repräsentieren, wird es für Anwendungsentwickler bedeutend schwieriger sicherzustellen, dass Fehlercodes quer über alle existierenden Programme einmalig sind. Es müsste eine Datenbank geben, in der alle Softwareentwickler die von ihnen benötigten Fehlercodes eintragen, so dass andere Softwareentwickler nicht die gleichen Fehlercodes für ganz andere Problemfälle verwenden. Da derartiges nicht praktikabel ist, gibt es Fehlerkategorien.

Fehlercodes vom Typ boost::system::error_code gehören immer einer Fehlerkategorie an, die über die Methode category() abgefragt werden kann. Betriebssystemfehler gehören dabei der Kategorie System an, die durch das vordefinierte Objekt boost::system::system_category repräsentiert wird.

Wenn Sie das obige Beispiel erweitern und category() aufrufen, erhalten Sie eine Referenz auf das vordefinierte Objekt boost::system::system_category. Für dieses kann zum Beispiel die Methode name() aufgerufen werden, um den Namen der Kategorie zu erhalten. Dieser lautet für das Objekt boost::system::system_category system.

#include <boost/system/error_code.hpp> 
#include <boost/asio.hpp> 
#include <iostream> 
#include <string> 

int main() 
{ 
  boost::system::error_code ec; 
  std::string hostname = boost::asio::ip::host_name(ec); 
  std::cout << ec.value() << std::endl; 
  std::cout << ec.category().name() << std::endl; 
} 

Fehler sind also über den Fehlercode und die Fehlerkategorie eindeutig identifizierbar. Weil Fehlercodes lediglich pro Kategorie eindeutig sein müssen, sollten Sie, wenn Sie für Ihr eigenes Programm Fehlercodes definieren wollen, eine neue Kategorie erstellen. So können Sie Fehlercodes beliebig vergeben, ohne dass es versehentlich zu Überschneidungen mit Fehlercodes anderer Entwickler kommt.

#include <boost/system/error_code.hpp> 
#include <iostream> 
#include <string> 

class application_category : 
  public boost::system::error_category 
{ 
public: 
  const char *name() const { return "application"; } 
  std::string message(int ev) const { return "error message"; } 
}; 

application_category cat; 

int main() 
{ 
  boost::system::error_code ec(14, cat); 
  std::cout << ec.value() << std::endl; 
  std::cout << ec.category().name() << std::endl; 
} 

Um eine eigene Fehlerkategorie zu definieren, müssen Sie eine neue Klasse erstellen und diese von boost::system::error_category ableiten. Dies erfordert, verschiedene Methoden zu implementieren, die durch die Schnittstelle von boost::system::error_category vorgegeben werden. Sie müssen auf alle Fälle zwei Methoden name() und message() definieren, wenn Sie verhindern wollen, dass Ihre Klasse abstrakt wird - denn diese Methoden sind als rein virtuelle Methoden in der Klasse boost::system::error_category definiert. Andere Methoden können, müssen aber nicht definiert werden, da boost::system::error_category Standarddefinitionen vorgibt.

Während name() den Namen der Fehlerkategorie zurückgibt, wird message() aufgerufen, um für eine Fehlernummer eine Fehlerbeschreibung zu erhalten. Üblicherweise wird dabei im Gegensatz zu obigem Beispiel der Parameter ev ausgewertet und je nach Fehlernummer eine andere Fehlerbeschreibung zurückgegeben.

Sie können ein Objekt vom Typ der neu definierten Fehlerkategorie verwenden, um mit ihm einen Fehlercode zu initialisieren. So wird im obigen Beispiel der Fehlercode ec mit der neuen Fehlerkategorie application_category definiert. Damit ist klar, dass in diesem Fall der Fehler 14 kein Systemfehler ist, sondern eine Bedeutung besitzt, die vom Entwickler der neuen Fehlerkategorie festgelegt wurde.

Die Klasse boost::system::error_code besitzt eine Methode default_error_condition(), die uns zur dritten Klasse führt, die Boost.System anbietet. Diese Methode gibt ein Objekt vom Typ boost::system::error_condition zurück. Dabei handelt es sich um eine Klasse, deren Schnittstelle fast identisch ist mit der von boost::system::error_code. Der einzige Unterschied ist die soeben erwähnte Methode default_error_condition(), die ausschließlich von der Klasse boost::system::error_code zur Verfügung gestellt wird.

#include <boost/system/error_code.hpp> 
#include <boost/asio.hpp> 
#include <iostream> 
#include <string> 

int main() 
{ 
  boost::system::error_code ec; 
  std::string hostname = boost::asio::ip::host_name(ec); 
  boost::system::error_condition ecnd = ec.default_error_condition(); 
  std::cout << ecnd.value() << std::endl; 
  std::cout << ecnd.category().name() << std::endl; 
} 

Da boost::system::error_condition der Klasse boost::system::error_code zum Verwechseln ähnlich sieht, wird sie genauso verwendet. So können Sie wie oben zu sehen auch für boost::system::error_condition die Methoden value() und category() aufrufen.

Der Grund, warum es zwei scheinbar identische Klassen gibt: Während die Klasse boost::system::error_code für plattformabhängige Fehlercodes verwendet wird, wird für plattformunabhängige Fehlercodes auf boost::system::error_condition zugegriffen. Und indem Sie die Methode default_error_condition() aufrufen, wird ein plattformabhängiger Fehlercode in einen plattformunabhängigen vom Typ boost::system::error_condition umgewandelt.

Wenn Sie obiges Programm ausführen, wird die Zahl 12 und als Name für die Fehlerkategorie GENERIC ausgegeben. Der plattformabhängige Fehlercode 14 wurde also in einen plattformunabhängigen Fehlercode 12 umgewandelt. Auch hierbei handelt es sich wie Sie sehen einfach nur um eine Zahl. Der entsprechende Fehler, der auf verschiedenen Plattformen auftreten kann, wird aber dank boost::system::error_condition plattformübergreifend durch die gleiche Zahl repräsentiert. Während unter Windows der entsprechende Fehler die 14 erhalten hat, wird er auf einem anderen Betriebssystem vielleicht durch die Zahl 25 repräsentiert. Wenn Sie mit boost::system::error_condition arbeiten, ist es aber plattformunabhängig die 12.

Die vierte und letzte Klasse, die Boost.System anbietet, ist boost::system::system_error. Sie ist von der Klasse std::runtime_error abgeleitet und ist daher ein Ausnahmetyp. Sie kann verwendet werden, wenn ein Fehlercode vom Typ boost::system::error_code in einer Ausnahme transportiert werden soll.

#include <boost/asio.hpp> 
#include <boost/system/system_error.hpp> 
#include <iostream> 

int main() 
{ 
  try 
  { 
    std::cout << boost::asio::ip::host_name() << std::endl; 
  } 
  catch (boost::system::system_error &e) 
  { 
    boost::system::error_code ec = e.code(); 
    std::cerr << ec.value() << std::endl; 
    std::cerr << ec.category().name() << std::endl; 
  } 
} 

Die freistehende Funktion boost::asio::ip::host_name() wird von Boost.Asio auch in einer Variante angeboten, die keinen Parameter vom Typ boost::system::error_code erwartet. Wenn diese Funktion ohne Parameter aufgerufen wird, wird im Falle eines Fehlers eine Ausnahme vom Typ boost::system::system_error geworfen. Die Ausnahme transportiert den Fehlercode vom Typ boost::system::error_code, der in der anderen Variante von boost::asio::ip::host_name() über einen Funktionsparameter zurückgegeben wird.


15.3 Boost.Exception

Die Bibliothek Boost.Exception stellt einen neuen Ausnahmetyp boost::exception vor, mit dem es möglich ist, Informationen zu einer Ausnahme hinzuzufügen, nachdem diese geworfen wurde. Die Klasse ist in der Headerdatei boost/exception/exception.hpp definiert. Da Boost.Exception Klassen und Funktionen über mehrere Headerdateien verteilt, wird im Folgenden auf boost/exception/all.hpp zugegriffen, ohne dass Headerdateien einzeln eingebunden werden müssen.

#include <boost/exception/all.hpp> 
#include <boost/lexical_cast.hpp> 
#include <boost/shared_array.hpp> 
#include <exception> 
#include <string> 
#include <iostream> 

typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info; 

class allocation_failed : 
  public boost::exception, 
  public std::exception 
{ 
public: 
  allocation_failed(std::size_t size) 
    : what_("allocation of " + boost::lexical_cast<std::string>(size) + " bytes failed") 
  { 
  } 

  virtual const char *what() const throw() 
  { 
    return what_.c_str(); 
  } 

private: 
  std::string what_; 
}; 

boost::shared_array<char> allocate(std::size_t size) 
{ 
  if (size > 1000) 
    throw allocation_failed(size); 
  return boost::shared_array<char>(new char[size]); 
} 

void save_configuration_data() 
{ 
  try 
  { 
    boost::shared_array<char> a = allocate(2000); 
    // saving configuration data ... 
  } 
  catch (boost::exception &e) 
  { 
    e << errmsg_info("saving configuration data failed"); 
    throw; 
  } 
} 

int main() 
{ 
  try 
  { 
    save_configuration_data(); 
  } 
  catch (boost::exception &e) 
  { 
    std::cerr << boost::diagnostic_information(e); 
  } 
} 

Im obigen Programm wird in main() eine Funktion save_configuration_data() aufgerufen, die wiederum eine Funktion allocate() aufruft. Die Funktion allocate() reserviert dynamisch Speicher, überprüft aber, ob eine bestimmte Grenze überschritten wird. Diese Grenze ist im Beispiel auf den willkürlichen Wert von 1000 Bytes gesetzt.

Wird nun allocate() mit einem Wert größer als 1000 aufgerufen, wird eine Ausnahme geworfen. Wie Sie anhand des Aufrufs von allocate() in der Funktion save_configuration_data() erkennen können, ist das hier genau der Fall. Die Funktion save_configuration_data() macht daher auch nicht wirklich viel - dass sie Konfigurationsdaten im dynamisch reservierten Speicher ablegen soll, wird lediglich mit einem Kommentar angedeutet.

Im obigen Programm soll eine Ausnahme geworfen werden, damit Boost.Exeption vorgestellt werden kann. Die Ausnahme, die in der Funktion allocate() geworfen wird, hat den Typ allocation_failed. Diese Klasse ist sowohl von boost::exception als auch von std::exception abgeleitet.

Die Ableitung von std::exception ist nicht notwendig. So könnte allocation_failed auch von einer Klasse aus einer anderen Klassenhierarchie abgeleitet worden sein, um sie in ein existierendes Framework einzubetten. Im obigen Programm wird auf die im C++ Standard definierte Klassenhierarchie für Ausnahmen zugegriffen. Natürlich würde es auch reichen, allocation_failed ausschließlich von boost::exception abzuleiten.

Wird eine Ausnahme vom Typ allocation_failed geworfen, wird die Größe des Speichers, der dynamisch reserviert hätte werden sollen, in der Ausnahme gespeichert. Das hat den Vorteil, dass beim Debuggen des Programms leicht überprüft werden kann, warum Speicher nicht reserviert werden konnte: Wurde mehr Speicher angefordert als von allocate() zur Verfügung gestellt werden kann, ist der Grund für die Ausnahme schnell gefunden.

Wenn Sie sich vorstellen, dass die Funktion allocate() in einem wesentlich größeren Programm eingesetzt wird und von vielen verschiedenen Funktionen aufgerufen wird, die alle dynamisch Speicher reservieren wollen, reicht die Information, welche Größe der dynamisch reservierte Speicher hätte haben sollen, nicht aus, um das Programm zu debuggen. In einem größeren Programm wäre es wichtig zu wissen, welche Funktion aus welchem Grund versucht hat, mehr Speicher anzufordern als allocate() bereitstellen kann. Im obigen Programm wird diese Funktion nur von save_configuration_data() aufgerufen. In einem größeren Programm stellt sich jedoch die Frage, wie mehr Informationen in die Ausnahme hineingepackt werden können, um das Debuggen zu erleichtern.

Die besondere Schwierigkeit ist, dass die Ausnahme in der Funktion allocate() auftritt, in dieser Funktion jedoch nicht genügend Informationen zur Verfügung stehen, um das Debuggen zu erleichtern. So weiß die Funktion allocate() nicht, wer sie aus welchem Grund aufgerufen hat, und kann nicht die entsprechenden Informationen in der Ausnahme zur Verfügung stellen.

Boost.Exception bietet folgende Lösung an: Informationen können jederzeit in eine Ausnahme hineingepackt werden. Dazu muss jeweils mit typedef ein auf boost::error_info basierender Datentyp definiert werden - und zwar für jede Information, die neu in eine Ausnahme hineingepackt werden soll.

boost::error_info ist ein Template und erwartet zwei Parameter: Der erste Parameter ist ein sogenannter Tag, der den neuen auf boost::error_info basierenden Datentypen eindeutig identifizierbar machen soll. Hier wird üblicherweise eine Struktur angegeben, die einen eindeutigen Namen bekommt. Der zweite Parameter bezieht sich auf den Datentyp der Information, die in der Ausnahme gespeichert werden soll.

Für obiges Programm bedeutet das, dass mit errmsg_info ein neuer Datentyp definiert wurde, der über die Struktur tag_errmsg eindeutig identifizierbar ist und einen String vom Typ std::string speichern kann.

Auf den Datentyp tag_errmsg wird im catch-Block der Funktion save_configuration_data() zugegriffen. Diese Funktion fängt Ausnahmen vom Typ boost::exception ab und fügt ihnen mit Hilfe des überladenen Operators operator<<() zusätzliche Informationen hinzu. Hierzu wird auf den neuen Datentyp tag_errmsg zugegriffen und ein Objekt erstellt, das mit dem String "saving configuration data failed" initialisiert wird. Auf diese Weise wird diese Information in die Ausnahme hineingepackt, die anschließend mit throw wieder geworfen wird.

Die Ausnahme transportiert nun nicht nur die Größe des Speichers, der hätte dynamisch reserviert werden sollen, sondern außerdem eine Beschreibung des Fehlers, die in der Funktion save_configuration_data() hinzugefügt wurde. Diese zusätzliche Beschreibung hilft beim Debuggen, da nun klar ist, von wo im Programm die Funktion allocate() aufgerufen wurde und wer versucht hat, mehr Speicher anzufordern als bereitgestellt werden kann.

Um alle in der Ausnahme vorhandenen Informationen abzurufen, kann wie im obigen Programm im catch-Block von main() geschehen die Funktion boost::diagnostic_information() aufgerufen werden. Diese Funktion ruft für die Ausnahme, die als Parameter übergeben wird, nicht nur what() auf, sondern greift auch auf alle zusätzlich hinzugefügten Informationen zu. boost::diagnostic_information() gibt einen String vom Typ std::string zurück, der dann wie im obigen Programm zum Beispiel auf die Standardfehlerausgabe ausgegeben werden kann.

Obiges Programm mit Visual C++ 2008 kompiliert gibt folgende Meldung aus:

Throw in function (unknown)
Dynamic exception type: class allocation_failed
std::exception::what: allocation of 2000 bytes failed
[struct tag_errmsg *] = saving configuration data failed

Wie Sie sehen wird der Datentyp der Ausnahme angegeben, die mit what() erhaltene Fehlermeldung und die Beschreibung plus dem dazugehörenden Namen der Struktur.

Wäre die Klasse allocation_failed nicht von std::exception abgeleitet worden, würde boost::diagnostic_information() nicht versuchen, die Methode what() aufzurufen. Innerhalb der Funktion boost::diagnostic_information() wird zur Laufzeit überprüft, ob ein Ausnahmetyp von std::exception abgeleitet ist und nur in diesem Fall what() aufgerufen.

Wenn Sie sich obige Meldung ansehen, stellen Sie fest, dass der Name der Funktion, die die Ausnahme vom Typ allocation_failed geworfen hat, nicht angegeben ist. Stattdessen heißt es, die Funktion wäre "unknown".

Boost.Exception stellt ein Makro zur Verfügung, mit dem eine Ausnahme derart geworfen werden kann, dass nicht nur der Name der entsprechenden Funktion in der Ausnahme gespeichert wird, sondern zusätzliche Informationen wie der Name der Datei und die Zeilennummer.

#include <boost/exception/all.hpp> 
#include <boost/lexical_cast.hpp> 
#include <boost/shared_array.hpp> 
#include <exception> 
#include <string> 
#include <iostream> 

typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info; 

class allocation_failed : 
  public std::exception 
{ 
public: 
  allocation_failed(std::size_t size) 
    : what_("allocation of " + boost::lexical_cast<std::string>(size) + " bytes failed") 
  { 
  } 

  virtual const char *what() const throw() 
  { 
    return what_.c_str(); 
  } 

private: 
  std::string what_; 
}; 

boost::shared_array<char> allocate(std::size_t size) 
{ 
  if (size > 1000) 
    BOOST_THROW_EXCEPTION(allocation_failed(size)); 
  return boost::shared_array<char>(new char[size]); 
} 

void save_configuration_data() 
{ 
  try 
  { 
    boost::shared_array<char> a = allocate(2000); 
    // saving configuration data ... 
  } 
  catch (boost::exception &e) 
  { 
    e << errmsg_info("saving configuration data failed"); 
    throw; 
  } 
} 

int main() 
{ 
  try 
  { 
    save_configuration_data(); 
  } 
  catch (boost::exception &e) 
  { 
    std::cerr << boost::diagnostic_information(e); 
  } 
} 

Wenn anstatt von throw das Makro BOOST_THROW_EXCEPTION verwendet wird, wird die Ausnahme um zusätzliche Informationen wie Funktionsname, Dateiname und Zeile ergänzt. Das funktioniert jedoch nur, wenn der Compiler entsprechende Makros unterstützt. Während Makros wie __FILE__ und __LINE__ im C++ Standard definiert sind, gibt es kein standardisiertes Makro, das den Namen der aktuellen Funktion zurückgibt. Da zahlreiche Compilerhersteller ein Makro für den Funktionsnamen definiert haben, versucht BOOST_THROW_EXCEPTION, den verwendeten Compiler zu erkennen und das entsprechende Makro zu verwenden. So gibt obiges Programm mit Visual C++ 2008 kompiliert folgende Meldung aus:

.\main.cpp(31): Throw in function class boost::shared_array<char> __cdecl allocate(unsigned int)
Dynamic exception type: class boost::exception_detail::clone_impl<struct boost::exception_detail::error_info_injector<class allocation_failed> >
std::exception::what: allocation of 2000 bytes failed
[struct tag_errmsg *] = saving configuration data failed

Wenn Sie sich obiges Programm genau ansehen, stellen Sie fest, dass die Klasse allocation_failed nicht mehr von boost::exception abgeleitet wurde. Trotzdem kann der Code problemlos kompiliert werden. Der Grund ist der, dass das Makro BOOST_THROW_EXCEPTION auf eine Funktion boost::enable_error_info() zugreift, die dynamisch erkennt, ob ein Ausnahmetyp von boost::exception abgeleitet ist und - wenn dies nicht der Fall ist - einen neuen Ausnahmetyp erstellt, der sowohl vom angegebenen Ausnahmetyp als auch von boost::exception abgeleitet wird. Das ist der Grund, warum obige Meldung als Ausnahmetyp nicht einfach nur allocation_failed ausgibt.

Abschließend wird Ihnen gezeigt, wie Sie gezielt auf eine zu einer Ausnahme hinzugefügten Information zugreifen.

#include <boost/exception/all.hpp> 
#include <boost/lexical_cast.hpp> 
#include <boost/shared_array.hpp> 
#include <exception> 
#include <string> 
#include <iostream> 

typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info; 

class allocation_failed : 
  public std::exception 
{ 
public: 
  allocation_failed(std::size_t size) 
    : what_("allocation of " + boost::lexical_cast<std::string>(size) + " bytes failed") 
  { 
  } 

  virtual const char *what() const throw() 
  { 
    return what_.c_str(); 
  } 

private: 
  std::string what_; 
}; 

boost::shared_array<char> allocate(std::size_t size) 
{ 
  if (size > 1000) 
    BOOST_THROW_EXCEPTION(allocation_failed(size)); 
  return boost::shared_array<char>(new char[size]); 
} 

void save_configuration_data() 
{ 
  try 
  { 
    boost::shared_array<char> a = allocate(2000); 
    // saving configuration data ... 
  } 
  catch (boost::exception &e) 
  { 
    e << errmsg_info("saving configuration data failed"); 
    throw; 
  } 
} 

int main() 
{ 
  try 
  { 
    save_configuration_data(); 
  } 
  catch (boost::exception &e) 
  { 
    std::cerr << *boost::get_error_info<errmsg_info>(e); 
  } 
} 

Im obigen Programm wird nicht wie zuvor boost::diagnostic_information() verwendet, sondern mit boost::get_error_info() gezielt auf die Fehlermeldung vom Typ errmsg_info zugegriffen. Da boost::get_error_info() einen smart pointer vom Typ boost::shared_ptr zurückgibt, muss über das Sternchen auf die Fehlermeldung zugegriffen werden. Nur für den Fall, dass der Parameter, der an boost::get_error_info() übergeben wird, nicht vom Typ boost::exception ist, ist der smart pointer ein Null-Zeiger. Wird zum Beispiel immer das Makro BOOST_THROW_EXCEPTION verwendet, um eine Ausnahme zu werfen, ist sichergestellt, dass der Ausnahmetyp von boost::exception abgeleitet ist - eine Überprüfung des smart pointers auf 0 ist dann nicht notwendig.